package nl.dxn.tunnel.server.proxy;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.flush.FlushConsolidationHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.*;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import nl.dxn.tunnel.server.Constants;
import nl.dxn.tunnel.server.config.HttpConfigProperties;
import nl.dxn.tunnel.server.handel.ClientHttpRequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

public class HttpServer {
    private static final Logger logger = LoggerFactory.getLogger(HttpServer.class);

    private final ServerBootstrap httpBootstrap = new ServerBootstrap();
    private final NioEventLoopGroup serverBossGroup = new NioEventLoopGroup();
    private final NioEventLoopGroup serverWorkerGroup = new NioEventLoopGroup();

    private final HttpConfigProperties httpConfigProperties;

    public HttpServer(final HttpConfigProperties httpConfigProperties) throws IOException {

        SslContext sslContext = null;
        if(httpConfigProperties.isHttps()) {
            sslContext = buildHttpsSslContext(httpConfigProperties);
        }

        this.httpConfigProperties = httpConfigProperties;
        SslContext finalSslContext = sslContext;
        httpBootstrap.group(serverBossGroup, serverWorkerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        if(finalSslContext !=null) {
                            // 创建 SSLEngine 并设置自定义的 HostnameVerifier
//                            SSLEngine sslEngine = finalSslContext.newEngine(ch.alloc());
//                            sslEngine.getSSLParameters().setEndpointIdentificationAlgorithm("HTTPS");
//                            SslHandler sslHandler = new SslHandler(sslEngine);
                            ch.pipeline().addLast(finalSslContext.newHandler(ch.alloc()));
//                            ch.pipeline().addLast(sslHandler);
                        }
                        ch.pipeline().addLast("MyHttpRequestDecoder", new HttpRequestDecoder());
                        //ch.pipeline().addLast("MyHttpResponseEncoder", new CsHttpsRequestHandler());
//                        ch.pipeline().addLast("MyHttpResponseEncoder", new HttpResponseEncoder());
//                        ch.pipeline().addLast("httpServerCodec",new HttpServerCodec());
                        ch.pipeline().addLast("ping", new IdleStateHandler(90, 100, 100 * 10, TimeUnit.SECONDS));
                        ch.pipeline().addLast("httpObjectAggregator",new HttpObjectAggregator(500 * 1024 * 1024));
                        ch.pipeline().addLast("chunkedWriteHandler",new ChunkedWriteHandler());
                        ch.pipeline().addLast(new FlushConsolidationHandler(5));
                        ch.pipeline().addLast(new ClientHttpRequestHandler(httpConfigProperties));
                    }
                });
    }

    public void destroy() {
        try {
            serverBossGroup.shutdownGracefully();
            serverWorkerGroup.shutdownGracefully();
        } catch (Exception ex) {
            logger.error("",ex);
        }
    }

    public boolean run() throws Exception {
        //启动服务器并绑定一个端口并且同步生成一个 ChannelFuture 对象
        ChannelFuture cf = httpBootstrap.bind(httpConfigProperties.getPort()).sync();
        if (cf.isSuccess()) {
            logger.info("http服务端启动成功,本机绑定IP:{},Http端口:{}", httpConfigProperties.getIp(), httpConfigProperties.getPort());
            return true;
        } else {
            logger.info("服务端启动失败,本机绑定IP:{},Http端口:{},原因:{}"
                    , httpConfigProperties.getIp(), httpConfigProperties.getPort(), cf.cause().getMessage());
            this.destroy();
            return false;
        }
    }

    private static SslContext buildHttpsSslContext(HttpConfigProperties httpConfigProperties) throws IOException {
        String domainCert = Optional.ofNullable(httpConfigProperties.getPemFileName()).orElse("https_server.pem");
        String domainKey = Optional.ofNullable(httpConfigProperties.getKeyFileName()).orElse("https_server.key");

        String contextPath = Constants.getContextPath();
        SslContextBuilder sslContextBuilder = SslContextBuilder
                .forServer(new File(contextPath,domainCert), new File(contextPath,domainKey))
                .trustManager(new File(contextPath,domainCert))
                .clientAuth(ClientAuth.NONE)
                .startTls(true).sslProvider(SslProvider.OPENSSL)
                .protocols("TLSv1","TLSv1.1","TLSv1.2")
                .trustManager(InsecureTrustManagerFactory.INSTANCE); // 使用不信任所有证书的 TrustManagerFactory

        return sslContextBuilder.build();
    }
}
